/**
 * Created on Apr 17, 2007
 * Created by Alan Snyder
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */

package com.aelitis.azureus.core.networkmanager.admin.impl;

import java.io.File;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.gudy.azureus2.core3.disk.DiskManager;
import org.gudy.azureus2.core3.disk.DiskManagerPiece;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerPeerListener;
import org.gudy.azureus2.core3.download.DownloadManagerState;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.peer.PEPeer;
import org.gudy.azureus2.core3.peer.PEPeerManager;
import org.gudy.azureus2.core3.security.SECertificateListener;
import org.gudy.azureus2.core3.security.SESecurityManager;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.util.AEDiagnostics;
import org.gudy.azureus2.core3.util.AEDiagnosticsLogger;
import org.gudy.azureus2.core3.util.AETemporaryFileHandler;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.SystemTime;
import org.gudy.azureus2.core3.util.TorrentUtils;
import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.download.Download;
import org.gudy.azureus2.plugins.download.DownloadException;
import org.gudy.azureus2.plugins.download.DownloadRemovalVetoException;
import org.gudy.azureus2.plugins.download.DownloadStats;
import org.gudy.azureus2.plugins.peers.Peer;
import org.gudy.azureus2.plugins.peers.PeerManager;
import org.gudy.azureus2.plugins.torrent.Torrent;
import org.gudy.azureus2.plugins.torrent.TorrentAttribute;
import org.gudy.azureus2.pluginsimpl.local.PluginCoreUtils;
import org.gudy.azureus2.pluginsimpl.local.PluginInitializer;
import org.gudy.azureus2.pluginsimpl.local.torrent.TorrentImpl;

import com.aelitis.azureus.core.networkmanager.NetworkManager;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdminSpeedTestScheduler;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdminSpeedTester;
import com.aelitis.azureus.core.networkmanager.admin.NetworkAdminSpeedTesterResult;

public class NetworkAdminSpeedTesterBTImpl extends NetworkAdminSpeedTesterImpl implements NetworkAdminSpeedTester {
    public static final String DOWNLOAD_AVE = "download-ave";
    public static final String UPLOAD_AVE = "upload-ave";
    public static final String DOWNLOAD_STD_DEV = "download-std-dev";
    public static final String UPLOAD_STD_DEV = "upload-std-dev";

    private static int testMode = TEST_TYPE_UPLOAD_ONLY;

    private static TorrentAttribute speedTestAttrib;

    private static NetworkAdminSpeedTesterResult lastResult;

    protected static void initialise() {
        PluginInterface plugin = PluginInitializer.getDefaultInterface();

        speedTestAttrib = plugin.getTorrentManager().getPluginAttribute(NetworkAdminSpeedTesterBTImpl.class.getName() + ".test.attrib");
    }

    protected static void startUp() {
        PluginInterface plugin = PluginInitializer.getDefaultInterface();

        org.gudy.azureus2.plugins.download.DownloadManager dm = plugin.getDownloadManager();
        Download[] downloads = dm.getDownloads();

        if (downloads != null) {
            int num = downloads.length;
            for (int i = 0; i < num; i++) {
                Download download = downloads[i];
                if (download.getBooleanAttribute(speedTestAttrib)) {
                    try {
                        if (download.getState() != Download.ST_STOPPED) {
                            try {
                                download.stop();
                            } catch (Throwable e) {
                                Debug.out(e);
                            }
                        }
                        download.remove(true, true);
                    } catch (Throwable e) {
                        Debug.out("Had " + e.getMessage() + " while trying to remove " + downloads[i].getName());
                    }
                }
            }
        }
    }

    protected static NetworkAdminSpeedTesterResult getLastResult() {
        return (lastResult);
    }

    private PluginInterface plugin;

    private boolean test_started;
    private boolean test_completed;

    private boolean use_crypto;

    private volatile boolean aborted;
    private String deferred_abort;

    /**
     * 
     * @param pi
     *            - PluginInterface is used to get Manager classes.
     */
    public NetworkAdminSpeedTesterBTImpl(PluginInterface pi) {
        plugin = pi;
    }

    public int getTestType() {
        return (NetworkAdminSpeedTestScheduler.TEST_TYPE_BT);
    }

    public void setMode(int mode) {
        testMode = mode;
    }

    public int getMode() {
        return (testMode);
    }

    public void setUseCrypto(boolean _use_crypto) {
        use_crypto = _use_crypto;
    }

    public boolean getUseCrypto() {
        return (use_crypto);
    }

    /**
     * The downloads have been stopped just need to do the testing.
     * 
     * @param tot
     *            - Torrent recieved from testing service.
     */
    public synchronized void start(TOTorrent tot) {
        if (test_started) {

            Debug.out("Test already started!");

            return;
        }

        test_started = true;

        // OK lets start the test.
        try {
            TorrentUtils.setFlag(tot, TorrentUtils.TORRENT_FLAG_LOW_NOISE, true);

            Torrent torrent = new TorrentImpl(tot);
            String fileName = torrent.getName();

            sendStageUpdateToListeners(MessageText.getString("SpeedTestWizard.stage.message.preparing"));

            // create a blank file of specified size. (using the temporary name.)
            File saveLocation = AETemporaryFileHandler.createTempFile();
            File baseDir = saveLocation.getParentFile();
            File blankFile = new File(baseDir, fileName);
            File blankTorrentFile = new File(baseDir, "speedTestTorrent.torrent");
            torrent.writeToFile(blankTorrentFile);

            URL announce_url = torrent.getAnnounceURL();

            if (announce_url.getProtocol().equalsIgnoreCase("https")) {

                SESecurityManager.setCertificateHandler(announce_url, new SECertificateListener() {
                    public boolean trustCertificate(String resource, X509Certificate cert) {
                        return (true);
                    }
                });
            }

            Download speed_download = plugin.getDownloadManager().addDownloadStopped(torrent, blankTorrentFile, blankFile);

            speed_download.setBooleanAttribute(speedTestAttrib, true);

            DownloadManager core_download = PluginCoreUtils.unwrap(speed_download);

            core_download.setPieceCheckingEnabled(false);

            // make sure we've got a bunch of upload slots

            core_download.getDownloadState().setIntParameter(DownloadManagerState.PARAM_MAX_UPLOADS, 32);
            core_download.getDownloadState().setIntParameter(DownloadManagerState.PARAM_MAX_UPLOADS_WHEN_SEEDING, 32);

            if (use_crypto) {

                core_download.setCryptoLevel(NetworkManager.CRYPTO_OVERRIDE_REQUIRED);
            }

            core_download.addPeerListener(new DownloadManagerPeerListener() {
                public void peerManagerWillBeAdded(PEPeerManager peer_manager) {
                    DiskManager disk_manager = peer_manager.getDiskManager();

                    DiskManagerPiece[] pieces = disk_manager.getPieces();

                    int startPiece = setStartPieceBasedOnMode(testMode, pieces.length);

                    for (int i = startPiece; i < pieces.length; i++) {
                        pieces[i].setDone(true);
                    }
                }

                public void peerManagerAdded(PEPeerManager peer_manager) {
                }

                public void peerManagerRemoved(PEPeerManager manager) {
                }

                public void peerAdded(PEPeer peer) {
                }

                public void peerRemoved(PEPeer peer) {
                }
            });

            speed_download.moveTo(1);

            speed_download.setFlag(Download.FLAG_DISABLE_AUTO_FILE_MOVE, true);

            core_download.initialize();

            core_download.setForceStart(true);

            TorrentSpeedTestMonitorThread monitor = new TorrentSpeedTestMonitorThread(speed_download);

            monitor.start();

            // The test has now started!!

        } catch (Throwable e) {

            test_completed = true;

            abort("Could not start test", e);
        }
    }

    public void complete(NetworkAdminSpeedTesterResult result) {
        sendResultToListeners(result);
    }

    protected void abort(String reason, Throwable cause) {
        String msg;

        if (cause instanceof RuntimeException) {

            msg = Debug.getNestedExceptionMessageAndStack(cause);

        } else {

            msg = Debug.getNestedExceptionMessage(cause);
        }

        abort(reason + ": " + msg);
    }

    public void abort(String reason) {
        reason = "Test aborted: " + reason;

        synchronized (this) {

            if (aborted) {

                return;
            }

            aborted = true;

            // we need to defer the reporting of a failure until the test is complete
            // as this prevents us from starting another test while the current one is
            // terminating

            if (test_started && !test_completed) {

                deferred_abort = reason;

                return;
            }
        }

        sendResultToListeners(new BitTorrentResult(reason));
    }

    /**
     * Get the result for
     * 
     * @return Result object of speed test.
     */
    public NetworkAdminSpeedTesterResult getResult() {
        return lastResult;
    }

    // ------------------ private methods ---------------

    /**
     * Depending on the mode we want to upload all the set all, none or only half the pieces to done.
     * 
     * @param mode
     *            - int that maps to NetworkAdminSpeedTestScheduler.TEST_TYPE...
     * @param totalPieces
     *            - total pieces in this test torrent.
     * @return - int - the starting piece number to setDone to true.
     */
    private static int setStartPieceBasedOnMode(int mode, int totalPieces) {

        // if(mode==TEST_TYPE_UPLOAD_AND_DOWNLOAD){
        // //upload half the pieces
        // return totalPieces/2;
        // }else
        if (mode == TEST_TYPE_UPLOAD_ONLY) {
            // upload all the pieces
            return 0;
        } else if (mode == TEST_TYPE_DOWNLOAD_ONLY) {
            // download all the pieces
            return totalPieces;
        } else
            throw new IllegalStateException("Did not recognize the NetworkAdmin Speed Test type. mode=" + mode);
    }

    /** -------------------- helper class to monitor test. ------------------- **/
    private class TorrentSpeedTestMonitorThread extends Thread {
        List historyDownloadSpeed = new LinkedList(); // <Long>
        List historyUploadSpeed = new LinkedList(); // <Long>
        List timestamps = new LinkedList(); // <Long>

        Download testDownload;

        public static final long MAX_TEST_TIME = 2 * 60 * 1000; // Limit test to 2 minutes.
        public static final long MAX_PEAK_TIME = 30 * 1000; // Limit to 30 seconds at peak.
        long startTime;
        long peakTime;
        long peakRate;

        public static final String AVE = "ave";
        public static final String STD_DEV = "stddev";

        public TorrentSpeedTestMonitorThread(Download d) {
            testDownload = d;
        }

        public void run() {
            try {
                Set connected_peers = new HashSet();
                Set not_choked_peers = new HashSet();
                Set not_choking_peers = new HashSet();

                try {

                    startTime = SystemTime.getCurrentTime();
                    peakTime = startTime;

                    boolean testDone = false;
                    long lastTotalTransferredBytes = 0;

                    sendStageUpdateToListeners(MessageText.getString("SpeedTestWizard.stage.message.starting"));
                    while (!(testDone || aborted)) {

                        int state = testDownload.getState();

                        if (state == Download.ST_ERROR) {

                            String enteredErrorState =
                                    MessageText.getString("SpeedTestWizard.abort.message.entered.error", new String[] { testDownload
                                            .getErrorStateDetails() });
                            abort(enteredErrorState);

                            break;
                        }

                        if (state == Download.ST_STOPPED) {

                            abort(MessageText.getString("SpeedTestWizard.abort.message.entered.queued"));

                            break;
                        }

                        // can flick out of force-mode when transitioning from downloading
                        // to seeding - easiest fix is:

                        if (!testDownload.isForceStart()) {

                            testDownload.setForceStart(true);
                        }

                        PeerManager pm = testDownload.getPeerManager();

                        if (pm != null) {

                            Peer[] peers = pm.getPeers();

                            for (int i = 0; i < peers.length; i++) {

                                Peer peer = peers[i];

                                // use the IP as the key so we don't count reconnects multiple times

                                String key = peer.getIp();

                                connected_peers.add(key);

                                if (!peer.isChoked()) {

                                    not_choked_peers.add(key);
                                }

                                if (!peer.isChoking()) {

                                    not_choking_peers.add(key);
                                }
                            }
                        }

                        long currTime = SystemTime.getCurrentTime();
                        DownloadStats stats = testDownload.getStats();
                        historyDownloadSpeed.add(autoboxLong(stats.getDownloaded(true)));
                        historyUploadSpeed.add(autoboxLong(stats.getUploaded(true)));
                        timestamps.add(autoboxLong(currTime));

                        updateTestProgress(currTime, stats);

                        lastTotalTransferredBytes = checkForNewPeakValue(stats, lastTotalTransferredBytes, currTime);

                        testDone = checkForTestDone();
                        if (testDone)
                            break;

                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException ie) {
                            // someone interrupted this thread for a reason. "test is now over"
                            abort(MessageText.getString("SpeedTestWizard.abort.message.interrupted"));

                            break;
                        }

                    }

                    // It is time to stop the test.
                    try {
                        if (testDownload.getState() != Download.ST_STOPPED) {
                            try {
                                testDownload.stop();
                            } catch (Throwable e) {
                                Debug.printStackTrace(e);
                            }
                        }
                        testDownload.remove(true, true);

                    } catch (DownloadException de) {

                        abort("TorrentSpeedTestMonitorThread could not stop the torrent " + testDownload.getName(), de);

                    } catch (DownloadRemovalVetoException drve) {

                        abort("TorrentSpeedTestMonitorTheard could not remove the torrent " + testDownload.getName(), drve);
                    }

                } catch (Exception e) {

                    abort(MessageText.getString("SpeedTestWizard.abort.message.execution.failed"), e);
                }

                if (!aborted) {

                    // check the stats for peers we connected to during the test
                    String connectStats =
                            MessageText.getString("SpeedTestWizard.stage.message.connect.stats", new String[] { "" + connected_peers.size(),
                                    "" + not_choked_peers.size(), "" + not_choking_peers.size() });
                    sendStageUpdateToListeners(connectStats);

                    if (connected_peers.size() == 0) {

                        abort(MessageText.getString("SpeedTestWizard.abort.message.failed.peers"));

                    } else if (not_choking_peers.size() == 0 && testMode != TEST_TYPE_DOWNLOAD_ONLY) {

                        abort(MessageText.getString("SpeedTestWizard.abort.message.insufficient.slots"));

                    } else if (not_choked_peers.size() == 0 && testMode != TEST_TYPE_UPLOAD_ONLY) {

                        abort(MessageText.getString("SpeedTestWizard.abort.message.not.unchoked"));
                    }
                }

                if (!aborted) {

                    // calculate the measured download rate.
                    NetworkAdminSpeedTesterResult r = calculateDownloadRate();

                    lastResult = r;

                    // TODO: persist it

                    // Log the result.
                    AEDiagnosticsLogger diagLogger = AEDiagnostics.getLogger("v3.STres");
                    diagLogger.log(r.toString());

                    complete(r);
                }
            } finally {

                synchronized (NetworkAdminSpeedTesterBTImpl.this) {

                    test_completed = true;

                    if (deferred_abort != null) {

                        sendResultToListeners(new BitTorrentResult(deferred_abort));
                    }
                }
            }
        }// run.

        /**
         * Calculate the test progression as a value between 0-100.
         * 
         * @param currTime
         *            - current time as long.
         * @param stats
         *            - Download stats
         */
        public void updateTestProgress(long currTime, DownloadStats stats) {

            // do two calculations. Frist based on the total time allowed for the test
            long totalDownloadTimeUsed = currTime - startTime;
            float percentTotal = ((float) totalDownloadTimeUsed / (float) MAX_TEST_TIME);

            // second for the time since the peak value has been reached.
            long totalTestTimeUsed = currTime - peakTime;
            float percentDownload = ((float) totalTestTimeUsed / (float) MAX_PEAK_TIME);

            // the larger of the two wins.
            float reportedProgress = percentTotal;
            if (percentDownload > reportedProgress)
                reportedProgress = percentDownload;

            int progressBarVal = Math.round(reportedProgress * 100.0f);
            StringBuffer msg = new StringBuffer("progress: ");
            msg.append(progressBarVal);
            // include the upload and download values.
            msg.append(" : download ave ");
            msg.append(stats.getDownloadAverage(true));
            msg.append(" : upload ave ");
            msg.append(stats.getUploadAverage(true));
            msg.append(" : ");
            int totalTimeLeft = (int) ((MAX_TEST_TIME - totalDownloadTimeUsed) / 1000);
            msg.append(totalTimeLeft);
            msg.append(" : ");
            int testTimeLeft = (int) ((MAX_PEAK_TIME - totalTestTimeUsed) / 1000);
            msg.append(testTimeLeft);

            sendStageUpdateToListeners(msg.toString());

        }// updateTestProgress

        /**
         * Calculate the avererage and standard deviation for a history.
         * 
         * @param history
         *            - List of Long values but that contains the sum downloaded at that time.
         * @return Map<String,Double> with values "ave" and "stddev" set
         */
        // Map<String,Double> calculate(List<Long> history)
        private Map calculate(List history) {

            // convert the list of long values that sum the value into a list of deltas.
            List deltas = convertSumToDeltas(history);

            // sort
            Collections.sort(deltas);

            // remove the top and bottom 10% of the sample. This removes outliers from the mean.
            final int nSamples = deltas.size();
            final int nRemove = nSamples / 10;

            for (int i = 0; i < nRemove; i++) {
                deltas.remove(0);
                deltas.remove(deltas.size() - 1);
            }

            // sum values
            long sumBytes = 0;
            int j = 0;
            while (j < deltas.size()) {
                sumBytes += autoboxLong(deltas.get(j));
                j++;
            }
            // calculate average.
            double aveRate = ((double) sumBytes) / deltas.size();
            // Debug.out("ave rate:"+aveRate);

            // calculate standard deviation.
            double variance = 0.0;
            double s;
            for (j = 0; j < deltas.size(); j++) {
                // Debug.out( j+","+deltas.get(j) );

                s = (autoboxLong(deltas.get(j)) - aveRate);
                variance += s * s;
            }
            double stddev = Math.sqrt(variance / (j - 1));

            // Map<String,Double> retVal = new HashMap<String,Double>();
            Map retVal = new HashMap();
            retVal.put(AVE, autoboxDouble(aveRate));
            retVal.put(STD_DEV, autoboxDouble(stddev));
            return retVal;
        }// calculate

        /**
         * Convert a list of sums into a list of download rates per second.
         * 
         * @param sumHistory
         *            - List<Long> with download sum for each second.
         * @return - List<Long> with the download rate for each second.
         */
        private List convertSumToDeltas(List sumHistory) {
            // find the first element to inlcude in the stat.
            int numStats = sumHistory.size();
            int i = findIndexPeak(numStats);

            List deltas = new ArrayList(numStats);
            if (i == 0) {
                return deltas;
            }
            long prevSumDownload = autoboxLong(sumHistory.get(i - 1));
            long currSumDownload;
            while (i < numStats) {

                currSumDownload = autoboxLong(sumHistory.get(i));
                Long currDelta = autoboxLong(currSumDownload - prevSumDownload);

                deltas.add(currDelta);
                i++;
                prevSumDownload = currSumDownload;
            }// while

            return deltas;
        }// convertSumToDeltas

        private int findIndexPeak(int numStats) {
            long thisTime;
            int i;
            for (i = 0; i < numStats; i++) {
                thisTime = autoboxLong(timestamps.get(i));
                if (thisTime > peakTime) {
                    break;
                }
            }// for
            return i;
        }

        /**
         * Based on the previous data cancluate an average and a standard deviation. Return this data in a Map object.
         * 
         * @return Map<String,Float> as a contain for stats. Map keys are "ave" and "dev".
         */
        NetworkAdminSpeedTesterResult calculateDownloadRate() {
            // calculate the BT download rate.
            // Map<String,Double> resDown = calculate(historyDownloadSpeed);
            Map resDown = calculate(historyDownloadSpeed);

            // calculate the BT upload rate.
            // Map<String,Double> resUp = calculate(historyUploadSpeed);
            Map resUp = calculate(historyUploadSpeed);

            return new BitTorrentResult(resUp, resDown);
        }// calculateDownloadRate

        /**
         * In this version the test is limited to MAX_TEST_TIME since the start of the test of MAX_PEAK_TIME (i.e. time since the peak download rate
         * has been reached). Which ever condition is first will finish the download.
         * 
         * @return true if the test done condition has been reached.
         */
        boolean checkForTestDone() {

            long currTime = SystemTime.getCurrentTime();
            // have we reached the max time for this test?
            if ((currTime - startTime) > MAX_TEST_TIME) {
                return true;
            }

            // have we been near the peak download value for max time?
            return (currTime - peakTime) > MAX_PEAK_TIME;
        }// checkForTestDone

        /**
         * We set a new "peak" value if it has exceeded the previous peak value by 10%.
         * 
         * @param stat
         *            -
         * @param lastTotalDownload
         *            -
         * @param currTime
         *            -
         * @return total downloaded so far.
         */
        long checkForNewPeakValue(DownloadStats stat, long lastTotalDownload, long currTime) {
            // upload only used the "uploaded" data. The "download only" and "both" uses download.
            long totTransferred;
            if (testMode == TEST_TYPE_UPLOAD_ONLY) {
                totTransferred = stat.getUploaded(true);
            } else {
                totTransferred = stat.getDownloaded(true);
            }
            long currTransferRate = totTransferred - lastTotalDownload;

            // if the current rate is 10% greater then the previous max, reset the max, and test timer.
            if (currTransferRate > peakRate) {
                peakRate = (long) (currTransferRate * 1.1);
                peakTime = currTime;
            }

            return totTransferred;
        }// checkForNewPeakValue

    }// class TorrentSpeedTestMonitorThread

    class BitTorrentResult implements NetworkAdminSpeedTesterResult {

        long time;
        int downspeed;
        int upspeed;
        boolean hadError = false;
        String lastError = "";

        /**
         * Build a Result for a successful test.
         * 
         * @param uploadRes
         *            - Map<String,Double> of upload results.
         * @param downloadRes
         *            - Map<String,Double> of download results.
         */
        public BitTorrentResult(Map uploadRes, Map downloadRes) {
            time = SystemTime.getCurrentTime();
            Double dAve = (Double) downloadRes.get(TorrentSpeedTestMonitorThread.AVE);
            Double uAve = (Double) uploadRes.get(TorrentSpeedTestMonitorThread.AVE);
            downspeed = dAve.intValue();
            upspeed = uAve.intValue();
        }

        /**
         * Build a Result if the test failed with an error.
         * 
         * @param errorMsg
         *            - why the test failed.
         */
        public BitTorrentResult(String errorMsg) {
            time = SystemTime.getCurrentTime();
            hadError = true;
            lastError = errorMsg;
        }

        public NetworkAdminSpeedTester getTest() {
            return (NetworkAdminSpeedTesterBTImpl.this);
        }

        public long getTestTime() {
            return time;
        }

        public int getDownloadSpeed() {
            return downspeed;
        }

        public int getUploadSpeed() {
            return upspeed;
        }

        public boolean hadError() {
            return hadError;
        }

        public String getLastError() {
            return lastError;
        }

        public String getResultString() {
            StringBuffer sb = new StringBuffer();

            // Time
            SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmss z");
            String d = format.format(new Date(time));
            sb.append(d).append(" ");

            sb.append("type: BT test ");

            // Get test info.
            sb.append("mode: ").append(getMode());

            // Get crypto
            sb.append(" encrypted: ");
            if (use_crypto) {
                sb.append("y");
            } else {
                sb.append("n");
            }

            if (hadError) {
                // Error
                sb.append(" Last Error: ").append(lastError);
            } else {
                // Result
                sb.append(" download speed: ").append(downspeed).append(" bits/sec");
                sb.append(" upload speed: ").append(upspeed).append(" bits/sec");
            }

            return sb.toString();
        }// getString

        public String toString() {
            StringBuffer sb = new StringBuffer("[com.aelitis.azureus.core.networkmanager.admin.impl.NetworkAdminSpeedTesterBTImpl");

            sb.append(" ").append(getResultString()).append(" ");
            sb.append("]");

            return sb.toString();
        }
    }// class BitTorrentResult

    private static long autoboxLong(Object o) {
        return autoboxLong((Long) o);
    }

    private static long autoboxLong(Long l) {
        return l.longValue();
    }

    private static Long autoboxLong(long l) {
        return new Long(l);
    }

    private static Double autoboxDouble(double d) {
        return new Double(d);
    }
}// class
